一、什么是注解
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
Java 注解用于为 Java 程序提供元数据。作为元数据,注解不直接影响代码的执行,但也有一些注解实际上可以用于这一目的。
什么是元数据,即一种描述数据的数据。所以可以说注解是描述源代码的数据。简单理解注解可以看出一个个标签,用来标记你的代码,是一种应用于类,方法,参数,变量,构造器及包的一种特殊修饰符。
注解和 class、interface 一样也是一种类型,通过 @interface 定义。如下:
使用:
二、元注解
元注解就是注解到注解上的注解,或者说元注解是一种基本注解,它能用来注解其他注解。我们可以将元注解看成一种特殊的修饰符,用来解释说明注解,它是注解的元数据。例如上例中的 @Retention 等。
元注解一共用 5 种:@Retention、@Documented、@Target、@Inherited、@Repeatable。
1、@Retention
Retention 意为保留期,@Retention 用来解释说明一个注解的存活周期。@Retention 取值:
- RetentionPolicy.SOURCE
注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 - RetentionPolicy.CLASS
注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 - RetentionPolicy.RUNTIME
注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
示例:
2、@Documented
用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。ANDROID 提供了的 NonNull 注解的源码如下:
3、@Target
指定注解应用的地方,用来限定注解的应用场景(类,方法,参数等等)。不使用 @Target 注解则默认不限制。其取值如下:
取值 | 描述 |
---|---|
ElementType.ANNOTATION_TYPE | 可以给一个注解进行注解 |
ElementType.CONSTRUCTOR | 可以给构造方法进行注解 |
ElementType.FIELD | 可以给属性进行注解 |
ElementType.LOCAL_VARIABLE | 可以给局部变量进行注解 |
ElementType.METHOD | 可以给方法进行注解 |
ElementType.PACKAGE | 可以给一个包进行注解 |
ElementType.PARAMETER | 可以给一个方法内的参数进行注解 |
ElementType.TYPE | 可以给一个类型进行注解,比如类、接口、枚举 |
示例:ANDROID 中的 @LayoutRes 注解,限定为布局资源。
4、@Inherited
标记这个注解是继承于哪个注解类。只能被用来标注 “Annotation 类型”,它所标注的 Annotation 具有继承性。当一个超类被 @Inherited 注解的注解(A 注解)进行过注解的话,如果它的子类没有被如何其他注解进行注解,那么这个子类就继承了超类的注解(A 注解)。示例如下:
TestAnnotation 被 @Retention 注解,类 TestA 被 @TestAnnotation 注解,类 TestB 继承类 TestA,那么类 TestB 也拥有 TestAnnotation注解。
@Inherited annotation 类型是被标注过的 class 的子类所继承。类并不从它所实现的接口继承 annotation,方法并不从它所重载的方法继承 annotation。
5、@Repeatable
@Repeatable 是自然可重复的意思,这是 Java 8 加进来的新特性。
在需要对同一种注解多次使用时,往往需要借助 @Repeatable。示例:
上面的代码 @Repeatable 注解了 Role ,@Repeatable 后面括号中的类相当于一个容器注解。
什么是容器注解?本身也是注解,用来存放其他注解。按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组。
@Role(role=”husband”),role=”husband” 表示给 Role 这个注解的 role 属性赋值,关于注解的属性下面会说明。Person 类需要多次使用 @Role 注解,所以这里使用 @Repeatable 注解 @Role。
测试一下注解效果:
打印如下:
三、注解的属性
注解的属性也叫成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无参的方法”的形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
注解的属性类型必须以下几种:8 种基本类型(byte、boolean、char、short、int、long、float、double)和 String、Enum、Class、annotation 类型,以及这些类型的数组。
上面代码定义 TestAnnotation 这个注解有 id 和 msg 两个属性。在使用的时候我们需要给它们赋值。
赋值方式:括号内以 value=“” 的形式赋值,多个属性以逗号隔开。
注解中可以设置默认值,默认值用 default 关键字指定。使用注解时对于指定了默认值的属性,如果不需要修改,可以不赋值。
当一个注解只有一个属性且属性名为 value 时,使用此注解可以省略括号内的属性名直接赋值:
如果注解没有属性,括号也可以省略:
四、JDK 内置注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中,外加 JDK 7 以后新增 3个,共 10 个。分别是:@Override、@Deprecated、@SuppressWarnings;@Retention、@Documented、@Target、@Inherited;@SafeVarargs、@FunctionalInterface、@Repeatable。
取值 | 描述 |
---|---|
作用在代码的注解: | |
@Override | 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 |
@Deprecated | 标记过时元素的注解,用来标识类,方法或者变量已过时,不建议使用。调用过时方法时编译器会提醒。 |
@SuppressWarnings | 指示编译器去忽略注解中声明的警告。 |
作用在其他注解的注解: | |
@Retention | 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在运行时可以通过反射访问。 |
@Documented | 标记这些注解是否包含在用户文档中。 |
@Target | 标记这个注解应该是哪种 Java 成员。 |
@Inherited | 标记这个注解是继承于哪个注解类(默认注解没有继承于任何子类) |
从 JAVA 7 开始新增: | |
@SafeVarargs | JAVA 7,参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。 |
@FunctionalInterface | JAVA 8,标识一个匿名函数或函数式接口。 |
@Repeatable | JAVA 8,标识某注解可以在同一个声明上使用多次。 |
五、注解的提取与应用
注解通过反射获取,通过 Class 对象的方法获取注解。常用的三个方法:
isAnnotationPresent
方法判断是否应用了某个注解。
getAnnotation
方法获取指定类型的注解。
getAnnotations
方法获取注解到当前元素上的所有注解。
以运行时注解为例:获取下面 TestAnnotation 注解的 name 属性内容,可以如下获取。
打印结果:
方法和属性也可以借助返回来获取注解:
打印结果:
注意:如果一个注解要想在运行时被提取,那么@Retention(RetentionPolicy.RUNTIME) 是必须的。
注解的应用:
- 提供信息给编译器:编译器可以通过注解来探测错误和警告信息。
- 编译阶段处理:软件工具可以利用注解信息来自动生成代码,HTML文档或者做其他相应处理。
- 运行时的处理: 某些注解可以在程序运行时接收代码的提取。
当开发者使用注解修饰了类,方法,变量等成员后,注解不会自己生效,必须由开发者提供对应的代码来提取处理注解信息。
这些用来提取和处理注解信息的代码统称为APT(Annotation Processing Tool),注解处理器,它用来在编译时扫描和处理注解。
总结来说,注解(Annotation)相对于一种标记,注解的应用就是编译器,开发工具或者程序通过反射来提取你的类和各种元素有无这种标记,有某种标记就去做相应的处理。